package arkham.gui;

import java.awt.*;
import java.awt.event.*;

import javax.swing.*;

/**
 * This class implements a sliding panel. Clicking on the tab causes the panel
 * to slide in and out. This panel only works if a child is defined. It will not
 * paint itself if no child is added. The add(Component comp) method must be used
 * to add children. The panel supports only one child at a time: adding a new child
 * will replace the old child component.
 * <br>
 * The size of the Sliding panel is directly influenced by the size of the child
 * component. It will only occupy as much space as its child component. Also, because
 * it uses setbounds to define its position, it is best used in a component where
 * there is no layout manager, such as in a JLayeredPane
 * @author ctheng
 *
 */
public class SlidingPanel extends JPanel
{
	public static final int SLIDE_LEFT = 0;
	public static final int SLIDE_RIGHT = 1;
	public static final int SLIDE_UP = 2;
	public static final int SLIDE_DOWN = 3;
	
	private int thickness;
	private boolean extended;
	private Component child;
	private int extendDirection;
	private int ax, ay;
	private Color tabColor, tabBgColor;
	
	public SlidingPanel(int direction, int ax, int ay)
	{
		this(direction, ax, ay, null);
	}
	
	public SlidingPanel(int direction, int ax, int ay, Component child)
	{
		super();
		extended = false;
		if (extendDirection < 0 || extendDirection > 3)
		{
			extendDirection = SLIDE_LEFT;
		}
		else
		{
			extendDirection = direction;
		}
		this.ax = ax;
		this.ay = ay;
		thickness = 15;
		if (child != null)
		{
			add(child);
		}
		tabColor = new Color(235, 235, 235);
		tabBgColor = new Color(192, 192, 192);
		
		init();
	}
	
	private void init()
	{
		setLayout(null);
		if (child == null)
		{
			switch (extendDirection)
			{
			case SLIDE_LEFT:
				setBounds(ax - thickness, ay, thickness, thickness);
				break;
			case SLIDE_RIGHT:
				setBounds(ax, ay, thickness, thickness);
				break;
			case SLIDE_UP:
				setBounds(ax, ay - thickness, thickness, thickness);
				break;
			case SLIDE_DOWN:
				setBounds(ax, ay, thickness, thickness);
				break;
			}
			return;
		}
		int w = child.getWidth();
		int h = child.getHeight();
		switch (extendDirection)
		{
		case SLIDE_LEFT:
			setBounds(ax - thickness, ay, thickness, h);
			break;
		case SLIDE_RIGHT:
			setBounds(ax, ay, thickness, h);
			break;
		case SLIDE_UP:
			setBounds(ax, ay - thickness, w, thickness);
			break;
		case SLIDE_DOWN:
			setBounds(ax, ay, w, thickness);
		}
		setOpaque(true);
		setVisible(true);
		addMouseListener(slidingPanelListener);
	}
	
	private void extend()
	{
		if (child == null || extended)
		{
			return;
		}
		int w = child.getWidth();
		int h = child.getHeight();
		switch (extendDirection)
		{
		case SLIDE_LEFT:
			setBounds(ax - w - thickness, ay, w + thickness, h);
			break;
		case SLIDE_RIGHT:
			setBounds(ax, ay, w + thickness, h);
			child.setBounds(0, 0, w, h);
			break;
		case SLIDE_UP:
			setBounds(ax, ay - h - thickness, w, h + thickness);
			break;
		case SLIDE_DOWN:
			setBounds(ax, ay, w, h + thickness);
			child.setBounds(0, 0, w, h);
		}
		getParent().repaint();
		extended = true;
	}
	
	private void contract()
	{
		if (child == null || !extended)
		{
			return;
		}
		int w = child.getWidth();
		int h = child.getHeight();
		switch (extendDirection)
		{
		case SLIDE_LEFT:
			setBounds(ax - thickness, ay, thickness, h);
			break;
		case SLIDE_RIGHT:
			setBounds(ax, ay, thickness, h);
			child.setBounds(-w, 0, w, h);
			break;
		case SLIDE_UP:
			setBounds(ax, ay - thickness, w, thickness);
			break;
		case SLIDE_DOWN:
			setBounds(ax, ay, w, thickness);
			child.setBounds(0, -h, w, h);
		}
		getParent().repaint();
		extended = false;
	}
	
	private MouseAdapter slidingPanelListener = new MouseAdapter()
	{
		public void mousePressed(MouseEvent e)
		{
			Point p = e.getPoint();
			if (p == null || child == null)
			{
				return;
			}
			switch (extendDirection)
			{
			case SLIDE_LEFT:
				if (p.x >= 0 && p.x < thickness && p.y >= 0 && p.y < child.getHeight())
				{
					setExtended(!extended);
				}
				break;
			case SLIDE_RIGHT:
				int rxpos = extended ? child.getWidth() : 0;
				if (p.x >= rxpos && p.x < rxpos + thickness && p.y >= 0 && p.y < child.getHeight())
				{
					setExtended(!extended);
				}
				break;
			case SLIDE_UP:
				if (p.x >= 0 && p.x < child.getWidth() && p.y >= 0 && p.y < thickness)
				{
					setExtended(!extended);
				}
				break;
			case SLIDE_DOWN:
				int rypos = extended ? child.getHeight() : 0;
				if (p.x >= 0 && p.x < child.getWidth() && p.y >= rypos && p.y < rypos + thickness)
				{
					setExtended(!extended);
				}
				break;
			}
			e.consume();
		}
	};
	
	@Override
	public void paintComponent(Graphics g)
	{
		if (child == null)
		{
			return;
		}
		super.paintComponent(g);
		// draw in extension arrows
		int w = child.getWidth();
		int h = child.getHeight();
		switch (extendDirection)
		{
		case SLIDE_LEFT:
			g.setColor(tabColor);
			g.fillRect(0, 0, thickness, h);
			g.setColor(tabBgColor);
			g.drawRect(0, 0, thickness - 1, h - 1);
			if (h < 50)
			{
				// draw one arrow
				if (extended)
				{
					drawArrow(g, thickness, h / 2, SLIDE_RIGHT);
				}
				else
				{
					drawArrow(g, 0, h / 2, SLIDE_LEFT);
				}
			}
			else
			{
				// draw 3 arrows
				for (int i = 1; i <= 3; i++)
				{
					int ypos = (h * i) / 4;
					if (extended)
					{
						drawArrow(g, thickness, ypos, SLIDE_RIGHT);
					}
					else
					{
						drawArrow(g, 0, ypos, SLIDE_LEFT);
					}
				}
			}
			break;
		case SLIDE_RIGHT:
			g.setColor(tabColor);
			int rxpos = extended ? w : 0;
			g.fillRect(rxpos, 0, thickness, h);
			g.setColor(tabBgColor);
			g.drawRect(rxpos, 0, thickness - 1, h - 1);
			if (h < 50)
			{
				// draw one arrow
				if (extended)
				{
					drawArrow(g, w, h / 2, SLIDE_LEFT);
				}
				else
				{
					drawArrow(g, thickness, h / 2, SLIDE_RIGHT);
				}
			}
			else
			{
				// draw 3 arrows
				for (int i = 1; i <= 3; i++)
				{
					int ypos = (h * i) / 4;
					if (extended)
					{
						drawArrow(g, w, ypos, SLIDE_LEFT);
					}
					else
					{
						drawArrow(g, thickness, ypos, SLIDE_RIGHT);
					}
				}
			}
			break;
		case SLIDE_UP:
			g.setColor(tabColor);
			g.fillRect(0, 0, w, thickness);
			g.setColor(tabBgColor);
			g.drawRect(0, 0, w - 1, thickness - 1);
			if (w < 50)
			{
				// draw one arrow
				if (extended)
				{
					drawArrow(g, w / 2, thickness, SLIDE_DOWN);
				}
				else
				{
					drawArrow(g, w / 2, 0, SLIDE_UP);
				}
			}
			else
			{
				// draw 3 arrows
				for (int i = 1; i <= 3; i++)
				{
					int xpos = (w * i) / 4;
					if (extended)
					{
						drawArrow(g, xpos, thickness, SLIDE_DOWN);
					}
					else
					{
						drawArrow(g, xpos, 0, SLIDE_UP);
					}
				}
			}
			break;
		case SLIDE_DOWN:
			int rypos = extended ? h : 0;
			g.setColor(tabColor);
			g.fillRect(0, rypos, w, thickness);
			g.setColor(tabBgColor);
			g.drawRect(0, rypos, w - 1, thickness - 1);
			if (w < 50)
			{
				// draw one arrow
				if (extended)
				{
					drawArrow(g, w / 2, h, SLIDE_UP);
				}
				else
				{
					drawArrow(g, w / 2, thickness, SLIDE_DOWN);
				}
			}
			else
			{
				// draw 3 arrows
				for (int i = 1; i <= 3; i++)
				{
					int xpos = (w * i) / 4;
					if (extended)
					{
						drawArrow(g, xpos, h, SLIDE_UP);
					}
					else
					{
						drawArrow(g, xpos, thickness, SLIDE_DOWN);
					}
				}
			}
			break;
		}
	}
	
	/*
	 * Note: The sliding panel may contain at most one component
	 * (non-Javadoc)
	 * @see java.awt.Container#add(java.awt.Component)
	 */
	@Override
	public Component add(Component comp)
	{
		removeAll();
		child = comp;
		if (child == null)
		{
			return null;
		}
		super.add(comp);
		switch (extendDirection)
		{
		case SLIDE_LEFT:
			comp.setBounds(thickness, 0, comp.getWidth(), comp.getHeight());
			break;
		case SLIDE_RIGHT:
			if (extended)
			{
				comp.setBounds(0, 0, comp.getWidth(), comp.getHeight());
			}
			else
			{
				comp.setBounds(-comp.getWidth(), 0, comp.getWidth(), comp.getHeight());
			}
			break;
		case SLIDE_UP:
			comp.setBounds(0, thickness, comp.getWidth(), comp.getHeight());
			break;
		case SLIDE_DOWN:
			if (extended)
			{
				comp.setBounds(0, 0, comp.getWidth(), comp.getHeight());
			}
			else
			{
				comp.setBounds(0, -comp.getHeight(), comp.getWidth(), comp.getHeight());
			}
			break;
		}
		return comp;
	}
	
	public boolean isExtended()
	{
		return extended;
	}
	
	/**
	 * Manually extend or contract the sliding panel
	 * @param toExtend set to true to extend, false to contract
	 */
	public void setExtended(boolean toExtend)
	{
		if (toExtend)
		{
			extend();
		}
		else
		{
			contract();
		}
	}
	
	/**
	 * Sets the tab color
	 * @param color the tab color
	 */
	public void setTabColor(Color color)
	{
		tabColor = color;
	}
	
	/**
	 * Sets the tab background color
	 * @param color the tab background color
	 */
	public void setTabBorderColor(Color color)
	{
		tabBgColor = color;
	}
	
	/**
	 * Draws and arrow pointing in the specific direction
	 * @param g the graphics context
	 * @param x the x coordinate of the edge of the box pointed to by the tip of
	 * the arrow
	 * @param y the y coordinate of the edge of the box pointed to by the tip of
	 * the arrow
	 * @param dir the direction of the arrow
	 */
	private void drawArrow(Graphics g, int x, int y, int dir)
	{
		Point p1, p2, p3;
		int size = thickness - 6;
		int width = (size * 2) / 3;
		switch (dir)
		{
		case SLIDE_LEFT:
			p1 = new Point(x + 3, y);
			p2 = new Point(x + 3 + size, y - width);
			p3 = new Point(x + 3 + size, y + width);
			break;
		case SLIDE_RIGHT:
			p1 = new Point(x - 3, y);
			p2 = new Point(x - 3 - size, y - width);
			p3 = new Point(x - 3 - size, y + width);
			break;
		case SLIDE_UP:
			p1 = new Point(x, y + 3);
			p2 = new Point(x - width, y + 3 + size);
			p3 = new Point(x + width, y + 3 + size);
			break;
		case SLIDE_DOWN:
			p1 = new Point(x, y - 3);
			p2 = new Point(x - width, y - 3 - size);
			p3 = new Point(x + width, y - 3 - size);
			break;
		default:
			return;
		}
		Polygon poly = new Polygon();
		poly.addPoint(p1.x, p1.y);
		poly.addPoint(p2.x, p2.y);
		poly.addPoint(p3.x, p3.y);
		g.setColor(Color.black);
		g.fillPolygon(poly);
	}
}
